//! Module for move generation and validation
//!
//! This module handles move generation, validation, and special rules
//! for different piece types in Chinese Chess.

use serde::{Serialize, Deserialize};
use crate::{Position, Side, ChessResult, ChessError};
use crate::board::Board;
use crate::piece::{Piece, PieceType, is_in_palace, has_crossed_river};

/// Represents a move from one position to another
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Move {
    pub from: Position,
    pub to: Position,
}

impl Move {
    /// Create a new move
    pub fn new(from: Position, to: Position) -> Self {
        Move { from, to }
    }
}

/// Direction vectors for piece movements
const ORTHOGONAL: [(i8, i8); 4] = [(0, 1), (1, 0), (0, -1), (-1, 0)];
const DIAGONAL: [(i8, i8); 4] = [(1, 1), (1, -1), (-1, 1), (-1, -1)];
const HORSE_MOVES: [(i8, i8); 8] = [
    (1, 2), (2, 1), (2, -1), (1, -2),
    (-1, -2), (-2, -1), (-2, 1), (-1, 2),
];

/// Move validation and generation functions
pub struct MoveGen;

impl MoveGen {
    /// Generate all legal moves for a given side
    pub fn generate_moves(board: &Board, side: Side) -> Vec<Move> {
        let mut moves = Vec::new();
        
        // Get all pieces of the given side
        for (pos, piece) in board.get_pieces(side) {
            moves.extend(Self::generate_piece_moves(board, pos));
        }
        
        moves
    }

    /// Generate all legal moves for a piece at the given position
    pub fn generate_piece_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        
        if let Some(piece) = board.get_piece(pos) {
            match piece.piece_type {
                PieceType::General => moves.extend(Self::general_moves(board, pos)),
                PieceType::Advisor => moves.extend(Self::advisor_moves(board, pos)),
                PieceType::Elephant => moves.extend(Self::elephant_moves(board, pos)),
                PieceType::Horse => moves.extend(Self::horse_moves(board, pos)),
                PieceType::Chariot => moves.extend(Self::chariot_moves(board, pos)),
                PieceType::Cannon => moves.extend(Self::cannon_moves(board, pos)),
                PieceType::Soldier => moves.extend(Self::soldier_moves(board, pos)),
            }
        }
        
        moves
    }

    /// Validate if a move is legal
    pub fn validate_move(board: &Board, mv: Move) -> ChessResult<()> {
        let piece = board.get_piece(mv.from)
            .ok_or(ChessError::InvalidMove)?;

        // Check if the destination is occupied by a friendly piece
        if let Some(target) = board.get_piece(mv.to) {
            if target.side == piece.side {
                return Err(ChessError::IllegalMove("Cannot capture own piece"));
            }
        }

        // Validate move based on piece type
        match piece.piece_type {
            PieceType::General => Self::validate_general_move(board, mv)?,
            PieceType::Advisor => Self::validate_advisor_move(board, mv)?,
            PieceType::Elephant => Self::validate_elephant_move(board, mv)?,
            PieceType::Horse => Self::validate_horse_move(board, mv)?,
            PieceType::Chariot => Self::validate_chariot_move(board, mv)?,
            PieceType::Cannon => Self::validate_cannon_move(board, mv)?,
            PieceType::Soldier => Self::validate_soldier_move(board, mv)?,
        }

        Ok(())
    }

    // Helper functions for move generation and validation
    fn general_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        // Orthogonal moves within palace
        for &(dx, dy) in &ORTHOGONAL {
            let new_pos = Position::new(pos.file + dx, pos.rank + dy)
                .filter(|p| is_in_palace(p, piece.side));
            
            if let Some(p) = new_pos {
                if board.get_piece(p).map_or(true, |target| target.side != piece.side) {
                    moves.push(Move::new(pos, p));
                }
            }
        }
        
        moves
    }

    fn advisor_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        // Diagonal moves within palace
        for &(dx, dy) in &DIAGONAL {
            let new_pos = Position::new(pos.file + dx, pos.rank + dy)
                .filter(|p| is_in_palace(p, piece.side));
            
            if let Some(p) = new_pos {
                if board.get_piece(p).map_or(true, |target| target.side != piece.side) {
                    moves.push(Move::new(pos, p));
                }
            }
        }
        
        moves
    }

    fn elephant_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        // Double diagonal moves
        for &(dx, dy) in &DIAGONAL {
            let mid_pos = Position::new(pos.file + dx, pos.rank + dy);
            let new_pos = Position::new(pos.file + dx * 2, pos.rank + dy * 2);
            
            if let (Some(mid), Some(new)) = (mid_pos, new_pos) {
                // Check elephant's eye is not blocked
                if board.is_empty(mid) && 
                   board.get_piece(new).map_or(true, |target| target.side != piece.side) {
                    // Elephant cannot cross river
                    if (piece.side == Side::Red && new.rank <= 4) ||
                       (piece.side == Side::Black && new.rank >= 5) {
                        moves.push(Move::new(pos, new));
                    }
                }
            }
        }
        
        moves
    }

    fn horse_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        for &(dx, dy) in &HORSE_MOVES {
            let new_pos = Position::new(pos.file + dx, pos.rank + dy);
            let leg_pos = Position::new(
                pos.file + dx.signum() * (dx.abs() == 2) as i8,
                pos.rank + dy.signum() * (dy.abs() == 2) as i8
            );
            
            if let (Some(new), Some(leg)) = (new_pos, leg_pos) {
                // Check horse's leg is not blocked
                if board.is_empty(leg) && 
                   board.get_piece(new).map_or(true, |target| target.side != piece.side) {
                    moves.push(Move::new(pos, new));
                }
            }
        }
        
        moves
    }

    fn chariot_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        // Orthogonal moves
        for &(dx, dy) in &ORTHOGONAL {
            let mut current = pos;
            while let Some(new_pos) = Position::new(current.file + dx, current.rank + dy) {
                match board.get_piece(new_pos) {
                    None => moves.push(Move::new(pos, new_pos)),
                    Some(target) => {
                        if target.side != piece.side {
                            moves.push(Move::new(pos, new_pos));
                        }
                        break;
                    }
                }
                current = new_pos;
            }
        }
        
        moves
    }

    fn cannon_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        // Orthogonal moves
        for &(dx, dy) in &ORTHOGONAL {
            let mut current = pos;
            let mut platform_found = false;
            
            while let Some(new_pos) = Position::new(current.file + dx, current.rank + dy) {
                match board.get_piece(new_pos) {
                    None => {
                        if !platform_found {
                            moves.push(Move::new(pos, new_pos));
                        }
                    }
                    Some(target) => {
                        if !platform_found {
                            platform_found = true;
                        } else if target.side != piece.side {
                            moves.push(Move::new(pos, new_pos));
                            break;
                        } else {
                            break;
                        }
                    }
                }
                current = new_pos;
            }
        }
        
        moves
    }

    fn soldier_moves(board: &Board, pos: Position) -> Vec<Move> {
        let mut moves = Vec::new();
        let piece = board.get_piece(pos).unwrap();
        
        let forward = match piece.side {
            Side::Red => 1,
            Side::Black => -1,
        };
        
        // Forward move
        if let Some(new_pos) = Position::new(pos.file, pos.rank + forward) {
            if board.get_piece(new_pos).map_or(true, |target| target.side != piece.side) {
                moves.push(Move::new(pos, new_pos));
            }
        }
        
        // Sideways moves after crossing river
        if has_crossed_river(&pos, piece.side) {
            for dx in [-1, 1].iter() {
                if let Some(new_pos) = Position::new(pos.file + dx, pos.rank) {
                    if board.get_piece(new_pos).map_or(true, |target| target.side != piece.side) {
                        moves.push(Move::new(pos, new_pos));
                    }
                }
            }
        }
        
        moves
    }

    // Move validation functions
    fn validate_general_move(board: &Board, mv: Move) -> ChessResult<()> {
        let piece = board.get_piece(mv.from).unwrap();
        
        if !is_in_palace(&mv.to, piece.side) {
            return Err(ChessError::IllegalMove("General must stay in the palace"));
        }
        
        let dx = (mv.to.file - mv.from.file).abs();
        let dy = (mv.to.rank - mv.from.rank).abs();
        
        if dx + dy != 1 {
            return Err(ChessError::IllegalMove("General can only move one step orthogonally"));
        }
        
        Ok(())
    }

    fn validate_advisor_move(board: &Board, mv: Move) -> ChessResult<()> {
        let piece = board.get_piece(mv.from).unwrap();
        
        if !is_in_palace(&mv.to, piece.side) {
            return Err(ChessError::IllegalMove("Advisor must stay in the palace"));
        }
        
        let dx = (mv.to.file - mv.from.file).abs();
        let dy = (mv.to.rank - mv.from.rank).abs();
        
        if dx != 1 || dy != 1 {
            return Err(ChessError::IllegalMove("Advisor can only move diagonally one step"));
        }
        
        Ok(())
    }

    fn validate_elephant_move(board: &Board, mv: Move) -> ChessResult<()> {
        let piece = board.get_piece(mv.from).unwrap();
        let dx = (mv.to.file - mv.from.file).abs();
        let dy = (mv.to.rank - mv.from.rank).abs();
        
        if dx != 2 || dy != 2 {
            return Err(ChessError::IllegalMove("Elephant must move exactly two steps diagonally"));
        }
        
        // Check elephant's eye is not blocked
        let mid_x = (mv.from.file + mv.to.file) / 2;
        let mid_y = (mv.from.rank + mv.to.rank) / 2;
        let mid_pos = Position::new(mid_x, mid_y).unwrap();
        
        if !board.is_empty(mid_pos) {
            return Err(ChessError::IllegalMove("Elephant's eye is blocked"));
        }
        
        // Check elephant doesn't cross river
        match piece.side {
            Side::Red if mv.to.rank > 4 => {
                return Err(ChessError::IllegalMove("Red elephant cannot cross the river"));
            }
            Side::Black if mv.to.rank < 5 => {
                return Err(ChessError::IllegalMove("Black elephant cannot cross the river"));
            }
            _ => Ok(()),
        }
    }

    fn validate_horse_move(board: &Board, mv: Move) -> ChessResult<()> {
        let dx = (mv.to.file - mv.from.file).abs();
        let dy = (mv.to.rank - mv.from.rank).abs();
        
        if !((dx == 1 && dy == 2) || (dx == 2 && dy == 1)) {
            return Err(ChessError::IllegalMove("Invalid horse move pattern"));
        }
        
        // Check horse's leg is not blocked
        let leg_x = mv.from.file + (mv.to.file - mv.from.file).signum() * (dx == 2) as i8;
        let leg_y = mv.from.rank + (mv.to.rank - mv.from.rank).signum() * (dy == 2) as i8;
        let leg_pos = Position::new(leg_x, leg_y).unwrap();
        
        if !board.is_empty(leg_pos) {
            return Err(ChessError::IllegalMove("Horse's leg is blocked"));
        }
        
        Ok(())
    }

    fn validate_chariot_move(board: &Board, mv: Move) -> ChessResult<()> {
        if mv.from.file != mv.to.file && mv.from.rank != mv.to.rank {
            return Err(ChessError::IllegalMove("Chariot must move orthogonally"));
        }
        
        let dx = (mv.to.file - mv.from.file).signum();
        let dy = (mv.to.rank - mv.from.rank).signum();
        let mut current = mv.from;
        
        while current != mv.to {
            current = Position::new(current.file + dx, current.rank + dy).unwrap();
            if current != mv.to && !board.is_empty(current) {
                return Err(ChessError::IllegalMove("Path is blocked"));
            }
        }
        
        Ok(())
    }

    fn validate_cannon_move(board: &Board, mv: Move) -> ChessResult<()> {
        if mv.from.file != mv.to.file && mv.from.rank != mv.to.rank {
            return Err(ChessError::IllegalMove("Cannon must move orthogonally"));
        }
        
        let dx = (mv.to.file - mv.from.file).signum();
        let dy = (mv.to.rank - mv.from.rank).signum();
        let mut current = mv.from;
        let mut platforms = 0;
        
        while current != mv.to {
            current = Position::new(current.file + dx, current.rank + dy).unwrap();
            if current != mv.to && !board.is_empty(current) {
                platforms += 1;
            }
        }
        
        match (platforms, board.is_empty(mv.to)) {
            (0, true) => Ok(()), // Normal move
            (1, false) => Ok(()), // Capture with exactly one platform
            _ => Err(ChessError::IllegalMove("Invalid cannon move")),
        }
    }

    fn validate_soldier_move(board: &Board, mv: Move) -> ChessResult<()> {
        let piece = board.get_piece(mv.from).unwrap();
        let dx = (mv.to.file - mv.from.file).abs();
        let dy = mv.to.rank - mv.from.rank;
        
        let forward = match piece.side {
            Side::Red => 1,
            Side::Black => -1,
        };
        
        if has_crossed_river(&mv.from, piece.side) {
            // Can move forward or sideways
            if dy == forward && dx == 0 || dy == 0 && dx == 1 {
                return Ok(());
            }
        } else {
            // Can only move forward
            if dy == forward && dx == 0 {
                return Ok(());
            }
        }
        
        Err(ChessError::IllegalMove("Invalid soldier move"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_general_moves() {
        let mut board = Board::new();
        let pos = Position { file: 4, rank: 0 };
        board.place_piece(pos, Piece::new(PieceType::General, Side::Red));
        
        let moves = MoveGen::generate_piece_moves(&board, pos);
        assert!(!moves.is_empty());
        
        // General should only be able to move within palace
        for mv in moves {
            assert!(is_in_palace(&mv.to, Side::Red));
        }
    }

    #[test]
    fn test_soldier_moves() {
        let mut board = Board::new();
        let pos = Position { file: 4, rank: 3 };
        board.place_piece(pos, Piece::new(PieceType::Soldier, Side::Red));
        
        let moves = MoveGen::generate_piece_moves(&board, pos);
        
        // Before crossing river, soldier can only move forward
        assert_eq!(moves.len(), 1);
        assert_eq!(moves[0].to.rank, pos.rank + 1);
        
        // After crossing river
        let pos = Position { file: 4, rank: 5 };
        board.place_piece(pos, Piece::new(PieceType::Soldier, Side::Red));
        
        let moves = MoveGen::generate_piece_moves(&board, pos);
        assert!(moves.len() > 1); // Can move forward and sideways
    }
}